package com.chapter.sample.redis.mylock;

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 实现Lock接口
 */
@Slf4j
public class RedisDistributedLock implements Lock {

    private StringRedisTemplate redisTemplate;

    private String lockName;
    private String uuidValue;
    private Long expireTime;

    public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockName) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        this.expireTime = 50L;
    }

    public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuid + ":" + Thread.currentThread().getId();
        this.expireTime = 30L;
    }

    @Override
    public void lock() {
        tryLock();
    }

    @Override
    public boolean tryLock() {
        try {
            tryLock(-1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time == -1) {
            String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                    "redis.call('hincrby', KEYS[1],ARGV[1],1) " +
                    "redis.call('expire',KEYS[1],ARGV[2]) " +
                    "return 1 " +
                    "else " +
                    "return 0 " +
                    "end";

            log.info("lock -> lockName：「{}」,uuidValue:「{}」", lockName, uuidValue);
            while (!redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                TimeUnit.MILLISECONDS.sleep(60);
            }
            //锁续期 - 新建一个后天扫描程序,来检测key目前的ttl,是否到规定的1/3 1/2的时间来实现续期
            renewExpire();
            return true;
        }
        return false;
    }

    private void renewExpire() {
        String script = "" +
                "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                    "return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else " +
                    "return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000) / 3);
    }

    @Override
    public void unlock() {
        log.info("lock -> lockName：「{}」,uuidValue:「{}」", lockName, uuidValue);
        String script = "" +
                "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then " +
                "return nil " +
                "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) ==0 then " +
                "return redis.call('del',KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        //nil = false  1 = true 0 = false
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
        if (null == flag) {
            throw new RuntimeException("this lock does not exists !");
        }
    }


    //中断情况 - 暂不用考虑
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    //condition情况 - 暂不用考虑
    @Override
    public Condition newCondition() {
        return null;
    }
}
